
package picsim;

import java.awt.EventQueue;
import javax.swing.ImageIcon;
import java.net.*;

/**
 *
 * @author Daniel
 * @brief Zentrale Klasse, die die Anwendung und alle weiteren Vorgänge steuert.
 *
 */
public class Control {

    // initial objects of classes
    private GUI gui;
    private FileMan fileMan;
    private Calculator calc;
    private PIC pic;
    private MemMan mem;
    private Comport comport;
    private Breakpoint bp;

    private final int SPEED_SLOW = 3000; // 3 sec
    private final int SPEED_NORMAL = 1500; // 1,5 sec
    private final int SPEED_FAST = 200; // 0,5 sec
    private static final String NAME_COMPORT = "COM1";

    private int simulationSpeed;

    private Thread comportThread;

    
    /**
     * @brief Konstruktor der Klasse Control, erzeugt Objekte der anderen Klassen (außer GUI)
     */
    public Control(GUI pGUI, MemMan pMemMan, PIC pPic, Breakpoint pBp) {

        gui = pGUI;
        mem = pMemMan;
        pic = pPic;
        bp = pBp;

        fileMan = new FileMan();
        calc = new Calculator();

        simulationSpeed = SPEED_NORMAL;

        Runnable comThread = new Runnable() {

            public void run() {
                comport = new Comport(NAME_COMPORT);
                gui.enableComportBox();
            }
        };
        
        comportThread = new Thread(comThread);
        comportThread.start();

        

        
    }

    /**
     * 
     * @param fileName (String)
     * @return icon (ImageIcon)
     * @brief Anhand des Parameters <code>fileName</code> wird das entsprechende Bild gesucht und als ImageIcon 
     * über <code>icon</code> zurückgegeben.
     */
    public ImageIcon getImageIcon(String fileName) {

        ClassLoader cldr = this.getClass().getClassLoader();

        URL imageURL = null;
        ImageIcon icon = null;

        String iconPath = "picsim/icons/";

        if(fileName.equals("open")) {
            imageURL = cldr.getResource(iconPath+"open.jpg");
            icon = new ImageIcon(imageURL, "Laden");
            return icon;
        }

        if(fileName.equals("reset")) {
            imageURL = cldr.getResource(iconPath+"reset.jpg");
            icon = new ImageIcon(imageURL, "Reset");
            return icon;
        }

        if(fileName.equals("play")) {
            imageURL = cldr.getResource(iconPath+"play.jpg");
            icon = new ImageIcon(imageURL, "Start");
            return icon;
        }

        if(fileName.equals("pause")) {
            imageURL = cldr.getResource(iconPath+"pause.jpg");
            icon = new ImageIcon(imageURL, "Pause");
            return icon;
        }

        if(fileName.equals("stop")) {
            imageURL = cldr.getResource(iconPath+"stop.jpg");
            icon = new ImageIcon(imageURL, "Stop");
            return icon;
        }

        if(fileName.equals("step")) {
            imageURL = cldr.getResource(iconPath+"step.jpg");
            icon = new ImageIcon(imageURL, "Next Step");
            return icon;
        }

        return icon;

    }

    // Methoden der Klasse "FileMan" aufrufen
    /**
     * @brief Dem Objekt der Klasse fileMan übermitteln, das Löschen des Dateipfads durchzuführen
     */
    public void clearFilePath() {
        fileMan.clearFilePath();
    }

    /**
     * @brief Steuert das Öffnen einer Datei (Öffnen-Dialog anzeigen, Anzeigen des Quelltextes, Ändern des Reset-Status
     */
    public void openFile() {

        if(fileMan.openFile()) {
            showSourceFileInGUI();
            mem.saveLastOpcode(fileMan.getLastOpcode());

            if(!fileMan.getFilePath().equals("")) {
                gui.setResetStatus(true);
            }
        }

    }

    /**
     * @brief Beauftragt die GUI die die vorbereitete Code Ansicht in das Textfeld der GUI einzusetzen
     */
    private void showSourceFileInGUI() {

        String code = fileMan.getCodeView();
        gui.setCodeView(code);

    }

    /**
     * @brief Setzt den Simulator (Speicher + GUI) nach Bestätigung einer Nachfrage auf Standardwerte zurück
     */
    public void resetSimulator() {

        int result = gui.showResetDialog();

        if(result == 0) { // ja
            fileMan.closeFile();
            mem.resetMemory();
            
            gui.resetGUI();
            gui.setResetStatus(false);

            setSimulationSpeed("normal");
            gui.setSpeedNormal();

            bp.deleteBreakpoints();

            gui.refreshGUI();
        }

        /*if(result == 1) System.out.println("Nein");
        if(result == 2) System.out.println("Abbrechen");
        if(result == JOptionPane.CLOSED_OPTION) System.out.println("closed");*/

    }
    // *********


    // Methoden der Klasse "PIC" aufrufen
    /**
     * @brief Startet das Ausführen eines PIC-Befehls und aktualisiert die GUI
     */
    public void run() {

        try {
            while(true) {

                nextStep(true);

                Thread.sleep(simulationSpeed); // wait
                
            }
        }
        catch (ThreadDeath td) {
            throw td;
        }
        catch(InterruptedException e) {
            // ...
            //e.printStackTrace();
        }        
        
    }
    // *********

    /**
     * 
     * @param testBreakpoints (boolean)
     * @brief Führt den nächsten Schritt des Programms aus.
     * Anhand des Parameters wird entschieden, ob Breakpoints beachtet werden.
     */
    public void nextStep(boolean testBreakpoints) {

        //int line = fileMan.findStartOfProgram(); // find first memory address and start with actualMemAddr found in
        String opcode = "";
        String tmp = "";

        // interrupts
        pic.interrupts();

        // prepare pcl for memory address method
        tmp = calc.fillToHexString(4, mem.getPCL());
        int line = fileMan.getLineOfMemoryAddress(tmp);
        opcode = fileMan.getOpcodeFromLST(line, false);


        if(!opcode.equals("null")) { // found correct opcode

            // getInput from comport
            if(gui.testComportCheckBox()) {
                communicateWithComport();
            }
            // *********

            // test line on breakpoints?
            if(testBreakpoints) {

                // breakpoint?
                if(bp.isBreakPoint(line)) {
                    System.out.println("bp gefunden -> "+line);

                    // highlighter
                    int start = fileMan.getStartOfLine(line);
                    int end = fileMan.getEndOfLine(line);
                    gui.highlightLine(start, end, 1); // yellow
                    // *********

                    gui.waitOnBreakpoint();

                }
                else {
                    System.out.println("kein bp -> "+line);

                    // highlighter
                    int start = fileMan.getStartOfLine(line);
                    int end = fileMan.getEndOfLine(line);
                    gui.highlightLine(start, end, 0); // blue
                    // *********
                }
            }
            else { // no breakpoint testing!
                // highlighter
                int start = fileMan.getStartOfLine(line);
                int end = fileMan.getEndOfLine(line);
                gui.highlightLine(start, end, 0); // blue
                // *********
            }

            exec(opcode, mem.getPCL());

            // TEST -> absatz
            System.out.println();
            // TEST
        }
        else {
            // ## TEST ##
            System.out.println("kein opcode ("+line+")");
            // ## TEST ##
        }

        gui.refreshGUI(); // show new content of gui

    }
    // *********


    /**
     * 
     * @param opcode (String)
     * @param actualMemAddr (int)
     * @brief Maskiert <code>opcode</code> und entscheidet anhand verschiedener Kriterien (Masken) welcher Befehl ausgeführt werden soll.
     * Herausfiltern aller Informationen, die im <code>opcode</code> enthalten sind und anschließende Ausführung des Befehls.
     */
    private void exec(String opcode, int actualMemAddr) {

        String instName = "";
        int regAddr = 0, bitAddr = 0;
        
        int mask = calc.getMask(opcode);

        int instruction = calc.getInstruction(mask, opcode);

         // operations with one parameter (0h or 0100h)
        if(instruction == 0x0000) {

            boolean bitSet = calc.isBitSet(opcode, 8);
            if(bitSet) { // in instruction movwf the destination bit is always set as 1
                instName = "movwf";
                regAddr = calc.getRegisterAddress(4, opcode);
                pic.executeOperation(instName, regAddr);

                // ### Tests: ###
                System.out.print("#Name des Befehls: ["+instName +"]: ");
                System.out.print("("+Integer.toHexString(instruction)+"h), ");
                System.out.print("Operant f: "+Integer.toHexString(regAddr)+"h = "+regAddr+"#");
                // ### ****** ###
            }
            else {
                instName = calc.getWellDefinedInstructionName(opcode); // now we can search for well-defined operations
                pic.executeOperation(instName);

               // ### Tests: ###
                System.out.print("#Name des Befehls: ["+instName +"]: ");
                System.out.print("("+Integer.toHexString(instruction)+"h), ");
                System.out.print("Operanten: -keine-#");
                // ### ****** ###
            }

        }        
        // we have to resolve whether clrf or clrw is meant
        else if(instruction == 0x0100) {
            boolean bitSet = calc.isBitSet(opcode, 8);
            if(bitSet) { // clrf
                instName = "clrf";
                regAddr = calc.getRegisterAddress(4, opcode);
                pic.executeOperation(instName, regAddr);

                // ### Tests: ###
                System.out.print("#Name des Befehls: ["+instName +"]: ");
                System.out.print("("+Integer.toHexString(instruction)+"h), ");
                System.out.print("Operant f: "+Integer.toHexString(regAddr)+"h = "+regAddr+"#");
                // ### ****** ###
            }
            else { // clrw
                instName = "clrw";
                pic.executeOperation(instName);

                // ### Tests: ###
                System.out.print("#Name des Befehls: ["+instName +"]: ");
                System.out.print("("+Integer.toHexString(instruction)+"h), ");
                System.out.print("Operanten: -keine-#");
                // ### ****** ###
            }
        }
        // now we can decide which of the bit-oriented operations we have to execute:
                        // bcf                  // bsf                      // btfsc                // btfss
        else if(instruction == 0x1000 || instruction == 0x1400 || instruction == 0x1800 || instruction == 0x1C00) {
            instName = calc.getInstructionName(instruction);
            regAddr = calc.getRegisterAddress(1, opcode); // get operand f
            bitAddr = calc.getRegisterAddress(5, opcode); // get operand b

            String bitAddr_16 = calc.fillToBinaryString(16, bitAddr);

            // extract b
            bitAddr_16 = bitAddr_16.substring(6, 9);
            bitAddr = Integer.parseInt(bitAddr_16,2);

            pic.executeOperation(instName, regAddr, bitAddr);

            // ### Tests: ###
            System.out.print("#Name des Befehls: ["+instName +"]: ");
            System.out.print("("+Integer.toHexString(instruction)+"h), ");
            System.out.print("Operant f = "+Integer.toHexString(regAddr)+"h  = "+regAddr+", Operant b = "+Integer.toHexString(bitAddr)+"#");
            // ### ****** ###
        }
        // is there an call or an goto operation to execute?
        else if(instruction == 0x2800 || instruction == 0x2000) {
            // call or goto

            if(instruction == 0x2800) {
                instName = "goto";
                regAddr = calc.getRegisterAddress(3, opcode);

                pic.executeOperation(instName, regAddr);


                // ### Tests: ###
                System.out.print("#Name des Befehls: ["+instName +"]: ");
                System.out.print("("+Integer.toHexString(instruction)+"h), ");
                System.out.print("Operant f: "+Integer.toHexString(regAddr)+"h = "+regAddr+"#");
                // ### ****** ###
            }
            else {
                instName = "call";
                regAddr = calc.getRegisterAddress(3, opcode);

                // save actual pcl+1 to stack
                String lastOpcode = mem.getLastOpcode();

                if(Integer.parseInt(lastOpcode, 16) > actualMemAddr) {
                    mem.push(actualMemAddr+1);
                }
                else {
                    mem.push(Integer.parseInt(lastOpcode, 16));
                }
                
                

                pic.executeOperation(instName, regAddr);

                // ### Tests: ###
                System.out.print("#Name des Befehls: ["+instName +"]: ");
                System.out.print("("+Integer.toHexString(instruction)+"h), ");
                System.out.print("Operant f: "+Integer.toHexString(regAddr)+"h = "+regAddr+"#");
                // ### ****** ###
            }

        }
        // all other operations can be found here...
        else {
            instName = calc.getInstructionName(instruction);
            
            if(instName.equals("xorlw") || instName.equals("iorlw") || instName.equals("addlw") || instName.equals("andlw") || instName.equals("movlw") || instName.equals("retlw") || instName.equals("sublw")) {
                regAddr = calc.getRegisterAddress(6, opcode);
            }
            else {
                regAddr = calc.getRegisterAddress(mask, opcode);
            }

            
            boolean destination = calc.isBitSet(opcode, 8);

            if(!(pic.executeOperation(instName, regAddr, destination))) {
                pic.executeOperation(instName, regAddr);
            }

            // ### Tests: ###
            System.out.print("#Name des Befehls: ["+instName +"]: ");
            System.out.print("("+Integer.toHexString(instruction)+"h), ");
            System.out.print("Operant d / f: "+destination+" / "+Integer.toHexString(regAddr)+"h = "+regAddr+"#");
            // ### ****** ###

        }

    }

    /**
     * @brief Die Methode gehört zu dem Teil, der die Simulation beendet und bildet die Schnittstelle zum Speicher.
     */
    public void stopSimulation() {

        mem.resetMemory();
        gui.refreshGUI();
        
    }


    /**
     * 
     * @param newSpeed (String)
     * @brief Anhand des übergebenen Strings <code>newSpeed</code> wird entschieden,
     * welche Simulationsgeschwindigkeit zu setzen ist.
     */
    public void setSimulationSpeed(String newSpeed) {

        if(newSpeed.equals("slow")) {
            simulationSpeed = SPEED_SLOW;
        }
        else if(newSpeed.equals("normal")) {
            simulationSpeed = SPEED_NORMAL;
        }
        else if(newSpeed.equals("fast")) {
            simulationSpeed = SPEED_FAST;
        }

    }

    /**
     * @brief Startet die Kommunikation mit der seriellen Schnittstelle.
     */
    public void communicateWithComport() {
        
        String trisA = Integer.toHexString(mem.getTrisARegValueSum());
        String portA = Integer.toHexString(mem.getPORTARegValueSum());

        String trisB = Integer.toHexString(mem.getTrisBRegValueSum());
        String portB = Integer.toHexString(mem.getPORTBRegValueSum());
        
        comport.communicateWithCom(trisA, portA, trisB, portB);

        // fill portA
        if(!comport.getInputPortA().isEmpty()) {

            try {
                int portA_new[] = calc.getSingleBitValues(5, Integer.parseInt(comport.getInputPortA(),16));

                for(int i=0; i<5; i++) {
                    mem.setPortARegValue(i, portA_new[i]);
                }
            }
            catch(NumberFormatException ex) {
                // ...
            }

        }
        

        // fill portB
        if(!comport.getInputPortB().isEmpty()) {

            try {
                int portB_new[] = calc.getSingleBitValues(8, Integer.parseInt(comport.getInputPortB(),16));
                for(int i=0; i<8; i++) {
                   mem.setPortBRegValue(i, portB_new[i]);
                }   
            }
            
            catch(NumberFormatException ex) {
                // ...
            }

        }
        
    }



    /**
     * @param args the command actualMemAddr arguments
     * @brief Start der gesamten Anwendung, erzeugt die GUI in einem neuen/eigenen Thread
     * Ohne den eigenen Thread würde die GUI "einfrieren" und so nicht mehr auf Interaktionen reagieren.
     */
    public static void main(String[] args) {

        // create new thread for GUI
        Runnable initGUI = new Runnable() {
            public void run() {
                new GUI(); // creates an object of class GUI
            }
        };

        // the runnable initGUI should be executed synchronously on the EventQueue
        // so we call invokeLater() to get this
        EventQueue.invokeLater(initGUI);

    }

}
